#include "StatefulSprite.h"
#include "SoundCache.h"
#include "SurfaceCache.h"

#include "Image.h"
#include <fstream>
#include "StringUtility.h"

using namespace StringUtility;

/* These are the collections of object definitions expected at the end of the level file */
enum StatefulSpriteFieldTypes {  evNotDefined,
                                 evFilename,
                                 evWidth,
                                 evHeight,
                                 evTransparentKey,
                                 evTransparentPoint,
                                 evNumStates,
                                 evState,
                                 evSound,
                                 evFrameSound,
                                 evEvent,
                                 evFrameEvent,
                                 evCollision,
                                 evCollisionFlags
                              };

// Map to associate the strings with the enum values
static std::map<std::string, StatefulSpriteFieldTypes> s_mapStatefulSpriteFieldTypes;
StatefulSprite::StatefulSprite()
{
    this->init();
};

StatefulSprite::StatefulSprite(string filename)
{
    this->init();
    load(filename);
};

StatefulSprite::~StatefulSprite()
{
    vector<CollisionRegion *>::iterator I;
    CollisionRegion* del = NULL;
    // we might have the same CollisionRegion twice in the vector
    // therefore we have to only delete every object once
    for (I = m_collisionRegions.begin(); I < m_collisionRegions.end(); ++I)
    {
        if ((*I) != del)
            delete del;
        del = (*I);
    }
    delete del;
    m_collisionRegions.clear();
    vector<AnimatedSprite*>::iterator K;
    for (K = m_sprites.begin(); K < m_sprites.end(); ++K)
    {
        delete (*K);
        (*K) = NULL;
    }
    m_sprites.clear();
    m_events.clear();
    m_frameEvents.clear();
    m_frameSounds.clear();
};

/* preload the map to allow us to effectively switch on a string */
void StatefulSprite::init()
{
    if (s_mapStatefulSpriteFieldTypes.size() == 0)
    {
        s_mapStatefulSpriteFieldTypes["Filename"] = evFilename;
        s_mapStatefulSpriteFieldTypes["Width"] = evWidth;
        s_mapStatefulSpriteFieldTypes["Height"] = evHeight;
        s_mapStatefulSpriteFieldTypes["TransparentKey"] = evTransparentKey;
        s_mapStatefulSpriteFieldTypes["TransparentPoint"] = evTransparentPoint;
        s_mapStatefulSpriteFieldTypes["NumStates"] = evNumStates;
        s_mapStatefulSpriteFieldTypes["State"] = evState;
        s_mapStatefulSpriteFieldTypes["Sound"] = evSound;
        s_mapStatefulSpriteFieldTypes["FrameSound"] = evFrameSound;
        s_mapStatefulSpriteFieldTypes["Event"] = evEvent;
        s_mapStatefulSpriteFieldTypes["FrameEvent"] = evFrameEvent;
        s_mapStatefulSpriteFieldTypes["Collision"] = evCollision;
        s_mapStatefulSpriteFieldTypes["CollisionFlags"] = evCollisionFlags;
    }
}
/*
PENJIN_ERRORS StatefulSprite::setTransparentColour(SDL_Surface *image, const Colour& c)
{
#ifdef PENJIN_SDL
    if(SDL_SetColorKey(image, SDL_SRCCOLORKEY | SDL_RLEACCEL, SDL_MapRGB(image->format,c.red,c.green,c.blue)) == -1)
        return PENJIN_SDL_INVALID_COLORKEY;
    return PENJIN_OK;
#endif
    return PENJIN_OK;
}

PENJIN_ERRORS StatefulSprite::setTransparentColour(SDL_Surface *image, const Vector2di& v)
{
#ifdef PENJIN_SDL
    Colour c = GFX::getPixel(image,v.x,v.y);
    return setTransparentColour(image,c);
#endif
    return PENJIN_OK;
}*/

// Load the sprite definition file, parse it, and prepare the sprites
void StatefulSprite::load(string filename)
{
    if (filename.find(".",0) == filename.npos)
    {
        filename += ".sprite";
    }
    m_filename = filename;
//    Factory *f = Factory::GetFactory();
    ifstream file(filename.c_str());
    string line;
    vector<string> split;
    string field;
    string spriteFilename = "";
    uint width;
    uint height;
    uint transparentColour;
    bool useTransparentPoint = false;
    Vector2di transparentPoint = Vector2di(0,0);
    uint sheetWidth;
    uint sheetHeight;
    uint numStates;

    string stateName = "";
    uint startIndex;
    uint numFrames;
    uint frameRate;
    //unsigned int slowdownFactor;

    AnimatedSprite *sprite = NULL;
    SDL_Surface *surface = NULL;
    Image image;
    CollisionRegion *region = NULL;

    SoundCache *sounds = SoundCache::GetSoundCache();
    string soundName;
    uint frameIndex;
    string eventName;

    bool surfaceFromCache = false;

    while (std::getline(file, line))
    {
        // If it is not a comment line
        if (line.size() > 0 and line.substr(0,1) != "#" and line.at(0) > 13)
        {
            if (line.at(line.size()-1) == 0x0D) line.erase(line.size()-1,1);
            // chop on equal into at most two parts
            tokenize(line,split,"=",2);
            field = split[0];
            if (split.size()<=1)
            {
                //apparently there is nothing after the equal, ignore the line
            }
            else
            {
                switch (s_mapStatefulSpriteFieldTypes[field])
                {
                case evFilename:
                    // New image, load it in
                    spriteFilename = split[1];
                    surface = SurfaceCache::GetSurfaceCache()->GetSurface(spriteFilename.c_str(),surfaceFromCache); //IMG_Load(spriteFilename.c_str());
                    if (!surface)
                    {
                        cout << "Critical error: reading " << spriteFilename << " failed!" << endl;
                        exit(-1);
                    }
                    break;
                case evWidth:
                    width = atoi(split[1].c_str());
                    // Figure out the number of xTiles
                    sheetWidth = surface->w/width;
                    break;
                case evHeight:
                    height = atoi(split[1].c_str());
                    // Figure out the number of yTiles
                    sheetHeight = surface->h/height;
                    break;
                case evTransparentKey:
                    transparentColour = atoi(split[1].c_str());
                    //if (surface && not surfaceFromCache) setTransparentColour(surface, Colour(transparentColour));
                    break;
                case evTransparentPoint:
                    line = split[1];
                    tokenize(line,split,",",2);
                    transparentPoint = Vector2di(atoi(split[0].c_str()),atoi(split[1].c_str()));
                    useTransparentPoint = true;
                    //if (surface && not surfaceFromCache) setTransparentColour(surface, transparentPoint);
                case evNumStates:
                    numStates = atoi(split[1].c_str());
                    break;
                case evState:
                    // It is a state, store a sprite for it
                    // If we don't have an image, width, or height, bail out
                    if ((spriteFilename == "") || (width == 0) || (height == 0))
                    {
                        // fall through to the break
                    }
                    else
                    {
                        // We've got what we need, parse the rest of the split
                        line = split[1];
                        tokenize(line,split,",",6);
                        // See if we have enough bits
                        if (split.size()>3)
                        {
                            stateName = split[0].c_str();
                            frameRate = atoi(split[1].c_str());
                            startIndex = atoi(split[2].c_str());
                            numFrames = atoi(split[3].c_str());
                            //slowdownFactor = atoi(split[3].c_str());
//                                surface = IMG_Load(spriteFilename.c_str());
//                                if (!surface) {
//                                    cout << "Error reading " << spriteFilename << endl;
//                                    exit(-1);
//                                }

                            sprite = new AnimatedSprite();
                            sprite->loadFrames(surface,sheetWidth,sheetHeight,startIndex,numFrames,false);
                            if (useTransparentPoint && not surfaceFromCache)
                            {
                                sprite->setTransparentColour(transparentPoint);
                            }
                            else
                            {
                                if (transparentColour != 0 && not surfaceFromCache)
                                {
                                    sprite->setTransparentColour(Colour(transparentColour));
                                }
                            }
                            if (frameRate > 0)
                            {
                                sprite->setFrameRate((TimerScalers)frameRate);
                            }
                            if (split.size() > 4)
                                sprite->setLooping(stringToBool(split[4].c_str()));
                            else
                                sprite->setLooping(true);
                            m_spriteStates.push_back(stateName);
                            m_sprites.push_back(sprite);
                            // also reference the current collision region
                            m_collisionRegions.push_back(region);
                        } // else fall through to break
                    }
                    break;
                case evSound:
                    line = split[1];
                    tokenize(line,split,",",3);
                    bool sPlay;
                    if (split.size() < 3)
                        sPlay = false;
                    else
                        sPlay = (bool)atoi(split[2].c_str());
                    sounds->AddSound(split[0],split[1],sPlay);
                    break;
                case evFrameSound:
                    line = split[1];
                    tokenize(line,split,",",3);
                    stateName = split[0];
                    frameIndex = atoi(split[1].c_str());
                    soundName = split[2];
                    m_frameSounds[make_pair(stateName,frameIndex)] = soundName;
                    break;
                case evEvent:
                    line = split[1];
                    // Peel off the first item to use as the eventName
                    tokenize(line,split,",",2);
                    eventName = split[0];
                    line = split[1];
                    tokenize(line,split,",");
                    m_events.insert(make_pair(eventName,split));
                    break;
                case evFrameEvent:
                    line = split[1];
                    tokenize(line,split,",",3);
                    stateName = split[0];
                    frameIndex = atoi(split[1].c_str());
                    line = split[2];
                    tokenize(line,split,",");
                    m_frameEvents.insert(make_pair(make_pair(stateName,frameIndex),split));
                    break;
                case evCollision: // Load the collision image and generate region
                {
                    bool dontNeedReturnValue;
                    line = split[1];
                    tokenize(line,split,",",5);
                    region = NULL;
                    region = new CollisionRegion;
                    SDL_Surface* surf = SurfaceCache::GetSurfaceCache()->GetSurface(split[0].c_str(),dontNeedReturnValue);
                    region->loadImage(surf,round(surf->w/atoi(split[2].c_str())),round(surf->h/atoi(split[3].c_str())),atoi(split[4].c_str()));
                    region->setNoCollisionColour(atoi(split[1].c_str()));
                    region->generateHitRegion();
                    break;
                }
                case evCollisionFlags: // Load collision flags
                {
                    line = split[1];
                    tokenize(line,split,",");
                    for (vector<string>::iterator I=split.begin(); I < split.end(); ++I)
                    {
                        int flag = CollisionFlags::stringToFlag((*I));
                        m_collisionFlags.addFlag(flag);
                        if (flag == CollisionFlags::USE_RECT)
                        {
                            region = new CollisionRegion;
                            region->setWidth(width);
                            region->setHeight(height);
                        }
                    }
                    break;
                }
                default:
                    break;
                } //switch
            } // if
        } // if
    } // while
}

// Manually add a SpriteState - for when we want to build one without loading from a .sprite file
void StatefulSprite::addSpriteState(string state, SDL_Surface* surface, uint sheetWidth, uint sheetHeight, uint startIndex, uint numFrames, uint frameRate, uint transparentColour, bool looping)
{
    AnimatedSprite *sprite;

    sprite = new AnimatedSprite();
    sprite->loadFrames(surface,sheetWidth,sheetHeight,startIndex,numFrames);
    if (transparentColour != 0)
    {
        sprite->setTransparentColour(Colour(transparentColour));
    }

    if (frameRate > 0)
    {
        sprite->setFrameRate((TimerScalers)frameRate);
    }
    sprite->setLooping(looping);

    m_spriteStates.push_back(state);
    m_sprites.push_back(sprite);
    /*m_spriteStates.resize(m_spriteStates.size()+1);
    m_spriteStates[m_spriteStates.size()-1] = stateName;
    m_sprites.resize(m_sprites.size()+1);
    m_sprites[m_sprites.size()-1] = sprite;*/
    // also reference the current collision region
    //m_collisionRegions.push_back(region);
}

// Locates the sprite for the given state
AnimatedSprite *StatefulSprite::getSpriteForState(string state)
{
    uint i;

    // We could do a search - but with a likely upper bound of twenty or so states at the most
    // a simple loop through the list is probably more efficient
    for (i=0; i<m_spriteStates.size(); i++)
    {
        if (m_spriteStates[i] == state)
        {
            // We've found the state, return the associated animated sprite
            m_sprites[i]->rewind();
            return m_sprites[i];
        }
    }

    // Failed to find the sprite; we could return NULL... or we could just return the first valid state:
    if (m_sprites.size() > 0)
    {
        cout << "Error: Could not find a state named '" + state + "' - returning default." << endl;
        m_sprites[0]->rewind();
        return m_sprites[0];
    }

    // Failing that, go ahead and return NULL. Alternately, we could reasonably die here...
    return NULL;
}

// Locates the collisionRegion for the given state (I shamelessly copied the function above)
CollisionRegion *StatefulSprite::getCollisionForState(string state)
{
    uint i;

    for (i=0; i<m_spriteStates.size(); i++)
    {
        if (m_spriteStates[i] == state)
        {
            return m_collisionRegions[i];
        }
    }

    if (m_collisionRegions.size() > 0)
    {
        cout << "Error: Could not find collision infor for a state named '" + state + "' - returning default." << endl;
        return m_collisionRegions[0];
    }

    return NULL;
}

void StatefulSprite::playSound(string soundName)
{
#ifdef _WIN32
    SoundCache::GetSoundCache()->winPlaySound(soundName); // else compiler reports missing function PlaySoundA O.o
#else
    SoundCache::GetSoundCache()->PlaySound(soundName);
#endif
};

void StatefulSprite::playFrameSound(string state, uint frameIndex)
{
    if (m_frameSounds.count(make_pair(state,frameIndex)) > 0)
    {
        playSound(m_frameSounds[make_pair(state,frameIndex)]);
    }
};

void StatefulSprite::playFrameSound(uint frameIndex)
{
    if (m_frameSounds.count(make_pair(m_currentState,frameIndex)) > 0)
    {
        playSound(m_frameSounds[make_pair(m_currentState,frameIndex)]);
    }
};

vector< vector<string> > StatefulSprite::getEvents(string eventName)
{
    vector< vector<string> > ret;
    multimap< string, vector<string> >::iterator it;
    pair<multimap< string, vector<string> >::iterator, multimap< string, vector<string> >::iterator> match;
    vector<string> cur;
    if (m_events.size() > 0)
    {
        match = m_events.equal_range(eventName);
        for (it=match.first; it!=match.second; ++it)
        {
            cur = it->second;
            ret.push_back(cur);
        }
    }
    return ret;
};

vector< vector<string> > StatefulSprite::getFrameEvents(string stateName, uint frameNumber)
{
    vector< vector<string> > result;
    multimap< pair<string, uint>, vector<string> >::iterator it;
    pair<multimap< pair<string, uint>, vector<string> >::iterator, multimap< pair<string, uint>, vector<string> >::iterator> match;
    vector<string> cur;

    if (m_frameEvents.size() > 0)
    {
        match = m_frameEvents.equal_range(make_pair(stateName,frameNumber));
        for (it=match.first; it!=match.second; ++it)
        {
            cur = it->second;
            result.push_back(cur);
        }
    }
    return result;
};
